package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFBaseProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.SignatureValidationException;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPOOBAuthFinishProcessor;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.OOBAuthSendContext;
import com.gmrz.uaf.servlet.v1.request.ReceiveReqPayload;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.util.Convert;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class UAPOOBAuthResponseHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(UAPIDAuthRequestHandler.class);

    /**
     *  {
     *     "uafResponse":"[{…}]",
     *     "context":{
     *          "transNo":"32characters",
     *          "appID","001",
     *          "userName":"123456",
     *          "authType":"12"
     *     }
     * }
     * @param request
     * @return
     * {
     *     "statusCode":1200,
     *     "description":"OK"
     * }
     */
    public String oobAuthFinish(String request){
        LOG.info("[/oob/send][begin]");
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        Gson gson = UAFSchemaBuilder.getGson();
        FidoRespPayload<String> respPayload = new FidoRespPayload<String>();
        Type reqType = new TypeToken<ReceiveReqPayload<OOBAuthSendContext>>() {}.getType();
        String response = null;
        try {
            ReceiveReqPayload<OOBAuthSendContext> reqPayload = gson.fromJson(request, reqType);
            ServerContext.getServerContext().setBase64(reqPayload.getContext().isBase64());
            List<String> authTypes =  reqPayload.getContext().getAuthType();
            boolean flag = false;
            if(null != authTypes) {
                for (String auth : authTypes) {
                    if(auth.compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0){
                        flag = true;
                    }
                }
            }
            if(flag){
                gson = UAFSchemaBuilder.getGsonProcessCert();
            }
            String uafMessage = null;
            if(ServerContext.getServerContext().isBase64()) {
                uafMessage = new String(Convert.fromBase64(reqPayload.getUafResponse()));
            }else {
                uafMessage = reqPayload.getUafResponse();
            }

            Type type = new TypeToken<List<AuthenticationResponse>>() {  } .getType();
            List<AuthenticationResponse> authResponses = gson.fromJson(uafMessage, type);
            AuthenticationResponse authResponse = null;
            if ((authResponses != null) && (!authResponses.isEmpty())) {
                if (authResponses.size() > 1) {
                    authResponse = RestValidator.compareAuthRespVersions(authResponses);
                } else {
                    authResponse = authResponses.get(0);
                }
            }
            //byte[] challenge = authResponse.getFinalChallengeParams().getServerChallenge().getChallenge();
            // hash 用户名
            String hashedUserName = generateHashedUserName(reqPayload.getContext().getUserName(), reqPayload.getContext().getRf1(), reqPayload.getContext().getRf2());
            validate(reqPayload,authResponse,hashedUserName);
            UAPOOBAuthFinishProcessor processor = GuiceUtil.getProcessorInjector()
                    .getInstance(UAPOOBAuthFinishProcessor.class);
            OOBAuthSendContext context = reqPayload.getContext();

            String authID = null;
            String authAppID = context.getAppID();
            String deviceID = null;

            boolean isMultipleAuth = false;
            String authType = null;
            if(null != authTypes){
                for(String auth : authTypes){
                    if(!auth.equals(Constants.AuthType.REMOTE_OOBAUTH.getAuthType())){
                        isMultipleAuth = true;
                        break;
                    }
                }
            }
            // 判断是否为非远程扫码认证，如果不是扫码认证则重新请求下【认证完成】接口
            if(isMultipleAuth) {
                if(com.gmrz.util.Strings.isNotBlank(context.getFunctionType()) && Constants.FUNCTION_TYPE_ACTIVITY.equals(context.getFunctionType())) {
                    String challenge = Convert.toBase64(authResponse.getFinalChallengeParams().getServerChallenge().getChallenge());
                    ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
                    challengeStoreService.setRedisValue(Constants.UAP_ACTIVITY_CACHE_KEY + challenge + "-" + hashedUserName, this.getServerConfig().getChallengeValiditySeconds(), "1");
                }
                UAFAuthResponseHandler uafAuthResponseHandler = new UAFAuthResponseHandler();
                Authenticator authenticator = null;
                if (flag) {
                    SignData signData = authResponse.getAuthenticatorAssertions().get(0).getSignResponse().getSignData();
                    String keyID = Convert.toBase64(signData.getKeyID());
                    String aaid = signData.getAAID();
                    UAFBaseProcessor uafBaseProcessor = GuiceUtil.getProcessorInjector().getInstance(UAFBaseProcessor.class);
                    authenticator = uafBaseProcessor.getAuthenticator(context.getAppID(), hashedUserName, aaid, keyID, context.getTransType());
                    deviceID = authenticator.getDeviceID();
                    authID = authenticator.getID();
                }
                // 将请求数据存入redis中
                saveAuthCommonParam(reqPayload,authResponse,hashedUserName);
                // 将请求数据再次走一遍【认证完成】的接口
                String uafAuthResponseHandlerResponse = uafAuthResponseHandler.authFinish(request);

//                logRequest(request,reqPayload.getContext().getTransNo(),reqPayload.getContext().getUserName(),
//                        reqPayload.getContext().getAppID(), Constants.AuthType.REMOTE_OOBAUTH.getAuthType(),reqPayload.getContext().getTransType(), Constants.Method.OOBAUTH_FINISH);

                JsonParser uafParser = new JsonParser();
                JsonObject responseJson = uafParser.parse(uafAuthResponseHandlerResponse).getAsJsonObject();
                JsonPrimitive statusCode = responseJson.getAsJsonPrimitive("statusCode");
                int statusCodeInt = statusCode.getAsInt();
                if (statusCodeInt == 1200) {
                    processor.updateOOBCode(context.getAuthToken(),authAppID,deviceID,Constants.OOBStatus.SERVER_AUTH.getStatus(),authID);
                    processor.process(reqPayload.getContext().getAuthToken(),reqPayload.getContext().getAuthData(),Constants.OOBStatus.SERVER_AUTH.getStatus());
                    if(com.gmrz.util.Strings.isNotBlank(context.getFunctionType()) && Constants.FUNCTION_TYPE_ACTIVITY.equals(context.getFunctionType())) {
                        OOBCodeBean oobCodeBean = processor.getOOBCode(context.getAuthToken());
                        if(context.getUserName().equals(oobCodeBean.getCustNO())) {
                            processor.updateCertStatus(context.getAppID(),authID,context.getActivateType(),authenticator);

                        }else {
                            throw new SignatureValidationException(UAFErrorCode.PROTOCOL_CUSTNO_ERROR);
                        }
                    }
                } else {
                    processor.updateOOBCode(context.getAuthToken(),authAppID,deviceID,Constants.OOBStatus.AUTH_FAIL.getStatus(),authID);
                    processor.process(reqPayload.getContext().getAuthToken(),reqPayload.getContext().getAuthData(),Constants.OOBStatus.AUTH_FAIL.getStatus());
                    return uafAuthResponseHandlerResponse;
                }
            }else {
                logRequest(request,reqPayload.getContext().getTransNo(),reqPayload.getContext().getUserName(),
                        reqPayload.getContext().getAppID(), Constants.AuthType.REMOTE_OOBAUTH.getAuthType(),reqPayload.getContext().getTransType(), Constants.Method.OOBAUTH_FINISH);
                processor.updateOOBCode(context.getAuthToken(),authAppID,deviceID,Constants.OOBStatus.APP_AUTH.getStatus(),authID);
                processor.process(authResponse, context.getAppID(), Constants.AuthType.REMOTE_OOBAUTH.getAuthType(), hashedUserName,context.getTransType(),context.getAuthToken(),context.getAuthData());
            }

            respPayload.setStatusCode(Constants.StatusCode.OK.getCode());
            respPayload.setDescription("OK");
            response =  gson.toJson(respPayload);
        } catch (Exception ex) {
            LOG.error("oobAuthFinish error",ex);
            response = buildFidoRespFromException(respPayload, ex);
        }
        LOG.info("[/oob/send][end]");
        return response;
    }

    private void saveAuthCommonParam(ReceiveReqPayload<OOBAuthSendContext> reqPayload,AuthenticationResponse authResponse,String hashedUserName) throws ChallengeStoreException {
        OOBAuthSendContext context = reqPayload.getContext();
        List<String> authTypes = reqPayload.getContext().getAuthType();
        if(null != authTypes) {
            Iterator<String> it = authTypes.iterator();
            while (it.hasNext()){
                String auth = it.next();
                if(auth.equals(Constants.AuthType.REMOTE_OOBAUTH.getAuthType())){
                    it.remove();
                }
            }
        }
        String challenge = Convert.toBase64(authResponse.getFinalChallengeParams().getServerChallenge().getChallenge());
        String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                context.getAppID(), null, context.getTransType(), context.getAuthType(),null);
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        challengeStoreService.setRedisValue(Constants.UAP_AUTH_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName, 300, param);
    }

    void validate(ReceiveReqPayload<OOBAuthSendContext> payload,AuthenticationResponse authResponse,String hashedUserName) throws PayloadValidationException, ChallengeStoreException {

        if ((payload == null) || (payload.getContext() == null)) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        if ((null == payload) || (Strings.isNullOrEmpty(payload.getUafResponse()))) {
            LOG.error("Payload does not contains UAFRegResponse");
            throw new PayloadValidationException("UAFRegResponse", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        List<String> authTypeList =  new ArrayList<String>();
        authTypeList.add(Constants.AuthType.REMOTE_OOBAUTH.getAuthType());
        OOBAuthSendContext context = payload.getContext();
        String challenge = Convert.toBase64(authResponse.getFinalChallengeParams().getServerChallenge().getChallenge());
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        String paramCache = challengeStoreService.getRedisValue(Constants.UAP_AUTH_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName);
        if (null == paramCache || "".equals(paramCache.trim())) {
            throw new PayloadValidationException("send context and receive context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
        }

        String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                context.getAppID(), null, context.getTransType(), authTypeList,null);
        param = param + ",authToken="+context.getAuthToken()+",authData="+context.getAuthData();
        LOG.debug("auth validate context param:" + param + "---paramcache" + paramCache);
        if (!param.equals(paramCache)) {
            throw new PayloadValidationException("send context and receive context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
        }
        RestValidator.validate("authToken", context.getAuthToken());;
        RestValidator.validate("authData", context.getAuthData());

    }
}
